package com.test.demo;

import org.apache.commons.lang3.StringUtils;

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.*;
import java.util.regex.Pattern;

/**
 * Created by jl on 2022/5/26 0026
 */
public class WordCounting {

    public static class WordNode {
        private String word;
        private int cnt;

        public WordNode(String word, int cnt) {
            this.word = word;
            this.cnt = cnt;
        }

        @Override
        public String toString() {
            return word + ": " + cnt;
        }
    }

    public static void main(String[] args) {
        JFrame.setDefaultLookAndFeelDecorated(true);
        JFrame frame = new JFrame("Words Frequency Counter");
        frame.setSize(800, 600);
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

        JPanel panel = new JPanel();
        frame.add(panel);
        panel.setLayout(null);

        JLabel jLabel = new JLabel("Min Chars Count: ");
        jLabel.setBounds(10, 20, 100, 25);
        panel.add(jLabel);

        JTextField minCountField = new JTextField(2);
        minCountField.setBounds(120, 20, 40, 25);
        panel.add(minCountField);

        JLabel jLabel2 = new JLabel("Max Chars Count: ");
        jLabel2.setBounds(260, 20, 120, 25);
        panel.add(jLabel2);

        JTextField maxCountField = new JTextField(2);
        maxCountField.setBounds(380, 20, 40, 25);
        panel.add(maxCountField);

        JLabel jLabel3 = new JLabel("Top N: ");
        jLabel3.setBounds(530, 20, 60, 25);
        panel.add(jLabel3);

        JTextField topNField = new JTextField(2);
        topNField.setBounds(580, 20, 40, 25);
        panel.add(topNField);

        JTextArea inputArea = new JTextArea();
        inputArea.setBounds(10, 60, 760, 200);
        panel.add(inputArea);

        JButton button = new JButton("Proceed");
        button.setBounds(100, 280, 120, 25);
        panel.add(button);

        JTextArea resultArea = new JTextArea();
        resultArea.setBounds(10, 380, 760, 200);
        resultArea.setLineWrap(true);
        panel.add(resultArea);

        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                int minCnt = StringUtils.isBlank(minCountField.getText()) ? 2 : Integer.parseInt(minCountField.getText());
                int maxCnt = StringUtils.isBlank(maxCountField.getText()) ? 2 : Integer.parseInt(maxCountField.getText());
                if (StringUtils.isBlank(inputArea.getText())) {
                    inputArea.setText("Place your context here first!");
                    return;
                }
                if (minCnt > maxCnt) {
                    resultArea.setText("Min chars count should't greater than  Max chars count!");
                    return;
                }
                int topN = StringUtils.isBlank(topNField.getText()) ? 10 : Integer.parseInt(topNField.getText());
                WordNode[] result = countWordsTopN(inputArea.getText(), topN, minCnt, maxCnt);
                resultArea.setText(Arrays.toString(result));
            }
        });

        frame.setVisible(true);
    }

    private static WordNode[] countWordsTopN(String content, int topN, int minLen, int maxLen) {
        WordNode[] result = new WordNode[topN];
        Map<String, WordNode> dict = new HashMap<>();
        Set<String> skipChars = new HashSet<>(Arrays.asList(",", "\n", " ", "\r\n", "。", "！", "：", "“", "”", "）", "（", "；",
                ":", "\"", "(", ")", "-", "_", "《", "》", "=", ".", "@", "\t", ">", "<", "/", "|","+","#","%","&"));
        char[] chars = content.toCharArray();
        for (int i = 0; i < chars.length; i++) {
            StringBuilder sb = new StringBuilder();
            for (int j = 0; j <= maxLen - 1 && i + j < chars.length; j++) {
                if (skipChars.contains(chars[i + j] + "")) {
                    break;
                }
                if (j + 1 >= minLen) {
                    sb.append(chars[i + j]);
                    count(dict, sb.toString(), result);
                } else if (j + 1 < minLen) {
                    sb.append(chars[i + j]);
                } else {
                    break;
                }
            }
        }
        return result;
    }

    private static boolean ignoreStartWithLetters = true;
    private static boolean ignoreStartWithNumbers = true;

    private static void count(Map<String, WordNode> dict, String word, WordNode[] result) {
        if (ignoreStartWithNumbers && isStartWithNumbers(word)) {
            return;
        }
        if (ignoreStartWithLetters && isStartWithLetters(word)) {
            return;
        }
        WordNode wordNode = dict.get(word);
        if (Objects.isNull(wordNode)) {
            wordNode = new WordNode(word, 1);
            dict.put(word, wordNode);
        } else {
            wordNode.cnt++;
        }

        int end = result.length - 1;
        // if added already
        for (int i = 0; i < result.length; i++) {
            if (result[i] == wordNode) {
                end = i;
                break;
            }
        }
        for (int i = end; i >= 0; i--) {
            WordNode cur = result[i];
            if (notBiggerThanCur(wordNode, cur)) {
                insertAndMove(result, wordNode, i + 1, end);
                break;
            } else if (i == 0) {
                insertAndMove(result, wordNode, 0, end);
                break;
            }
        }
    }

    private static final Pattern PATTERN_NUMBER = Pattern.compile("^[0-9]+.*$");
    private static boolean isStartWithNumbers(String word) {
        return PATTERN_NUMBER.matcher(word).matches();
    }

    private static final Pattern PATTERN_LETTER = Pattern.compile("^[a-zA-z]+.*$");
    private static boolean isStartWithLetters(String word) {
        return PATTERN_LETTER.matcher(word).matches();
    }

    private static void insertAndMove(WordNode[] result, WordNode wordNode, int i, int end) {
        if (i >= end + 1) {
            return;
        }
        for (int j = end - 1; j >= i; j--) {
            result[j + 1] = result[j];
        }
        result[i] = wordNode;
    }

    private static boolean notBiggerThanCur(WordNode wordNode, WordNode cur) {
        if (Objects.isNull(cur) || wordNode == cur) {
            return false;
        }
        return cur.cnt >= wordNode.cnt;
    }
}
